package com.asggo.g21.utils;

import cn.hutool.extra.spring.SpringUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.redisson.api.GeoEntry;
import org.redisson.api.GeoPosition;
import org.redisson.api.GeoUnit;
import org.redisson.api.ObjectListener;
import org.redisson.api.RAtomicLong;
import org.redisson.api.RBatch;
import org.redisson.api.RBucket;
import org.redisson.api.RBucketAsync;
import org.redisson.api.RGeo;
import org.redisson.api.RJsonBucket;
import org.redisson.api.RKeys;
import org.redisson.api.RList;
import org.redisson.api.RMap;
import org.redisson.api.RMapAsync;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RSet;
import org.redisson.api.RTopic;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RateType;
import org.redisson.api.RedissonClient;
import org.redisson.api.geo.GeoSearchArgs;
import org.redisson.codec.JacksonCodec;

/**
 * Created by IntelliJ IDEA.
 *
 * @author eric 2023/12/15 17:22
 */
public class RedisUtils {

  private RedisUtils() {
  }

  private static final RedissonClient redissonClient = SpringUtil.getBean(RedissonClient.class);

  /**
   * 限流
   *
   * @param key          限流key
   * @param rateType     限流类型
   * @param rate         速率
   * @param rateInterval 速率间隔
   * @return -1 表示失败
   */
  public static long rateLimiter(String key, RateType rateType, int rate, int rateInterval) {
    RRateLimiter rateLimiter = redissonClient.getRateLimiter(key);
    rateLimiter.trySetRate(rateType, rate, rateInterval, RateIntervalUnit.SECONDS);
    if (rateLimiter.tryAcquire()) {
      return rateLimiter.availablePermits();
    } else {
      return -1L;
    }
  }

  /**
   * 获取客户端实例
   */
  public static RedissonClient getClient() {
    return redissonClient;
  }

  /**
   * 发布通道消息
   *
   * @param channelKey 通道key
   * @param msg        发送数据
   * @param consumer   自定义处理
   */
  public static <T> void publish(String channelKey, T msg, Consumer<T> consumer) {
    RTopic topic = redissonClient.getTopic(channelKey);
    topic.publish(msg);
    consumer.accept(msg);
  }

  public static <T> void publish(String channelKey, T msg) {
    RTopic topic = redissonClient.getTopic(channelKey);
    topic.publish(msg);
  }

  /**
   * 订阅通道接收消息
   *
   * @param channelKey 通道key
   * @param clazz      消息类型
   * @param consumer   自定义处理
   */
  public static <T> void subscribe(String channelKey, Class<T> clazz, Consumer<T> consumer) {
    RTopic topic = redissonClient.getTopic(channelKey);
    topic.addListener(clazz, (channel, msg) -> consumer.accept(msg));
  }

  /**
   * 缓存基本的对象，Integer、String、实体类等
   *
   * @param key   缓存的键值
   * @param value 缓存的值
   */
  public static <T> void setCacheObject(final String key, final T value) {
    setCacheObject(key, value, false);
  }

  /**
   * 缓存基本的对象，保留当前对象 TTL 有效期
   *
   * @param key       缓存的键值
   * @param value     缓存的值
   * @param isSaveTtl 是否保留TTL有效期(例如: set之前ttl剩余90 set之后还是为90)
   * @since Redis 6.X 以上使用 setAndKeepTTL 兼容 5.X 方案
   */
  public static <T> void setCacheObject(final String key, final T value, final boolean isSaveTtl) {
    RBucket<T> bucket = redissonClient.getBucket(key);
    if (isSaveTtl) {
      try {
        bucket.setAndKeepTTL(value);
      } catch (Exception e) {
        long timeToLive = bucket.remainTimeToLive();
        setCacheObject(key, value, Duration.ofMillis(timeToLive));
      }
    } else {
      bucket.set(value);
    }
  }

  /**
   * 缓存基本的对象，Integer、String、实体类等
   *
   * @param key      缓存的键值
   * @param value    缓存的值
   * @param duration 时间
   */
  public static <T> void setCacheObject(final String key, final T value, final Duration duration) {
    RBatch batch = redissonClient.createBatch();
    RBucketAsync<T> bucket = batch.getBucket(key);
    bucket.setAsync(value);
    bucket.expireAsync(duration);
    batch.execute();
  }

  /**
   * 如果不存在则设置 并返回 true 如果存在则返回 false
   *
   * @param key   缓存的键值
   * @param value 缓存的值
   * @return set成功或失败
   */
  public static <T> boolean setObjectIfAbsent(final String key, final T value,
      final Duration duration) {
    RBucket<T> bucket = redissonClient.getBucket(key);
    return bucket.setIfAbsent(value, duration);
  }

  /**
   * 注册对象监听器
   * <p>
   * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
   *
   * @param key      缓存的键值
   * @param listener 监听器配置
   */
  public static <T> void addObjectListener(final String key, final ObjectListener listener) {
    RBucket<T> result = redissonClient.getBucket(key);
    result.addListener(listener);
  }

  /**
   * 设置有效时间
   *
   * @param key     Redis键
   * @param timeout 超时时间
   * @return true=设置成功；false=设置失败
   */
  public static boolean expire(final String key, final long timeout) {
    return expire(key, Duration.ofSeconds(timeout));
  }

  /**
   * 设置有效时间
   *
   * @param key      Redis键
   * @param duration 超时时间
   * @return true=设置成功；false=设置失败
   */
  public static boolean expire(final String key, final Duration duration) {
    RBucket<Object> rBucket = redissonClient.getBucket(key);
    return rBucket.expire(duration);
  }

  /**
   * 获得缓存的基本对象。
   *
   * @param key 缓存键值
   * @return 缓存键值对应的数据
   */
  public static <T> T getCacheObject(final String key) {
    RBucket<T> rBucket = redissonClient.getBucket(key);
    return rBucket.get();
  }

  /**
   * 获得key剩余存活时间
   *
   * @param key 缓存键值
   * @return 剩余存活时间
   */
  public static <T> long getTimeToLive(final String key) {
    RBucket<T> rBucket = redissonClient.getBucket(key);
    return rBucket.remainTimeToLive();
  }

  /**
   * 删除单个对象
   *
   * @param key 缓存的键值
   */
  public static boolean deleteObject(final String key) {
    return redissonClient.getBucket(key).delete();
  }

  /**
   * 删除集合对象
   *
   * @param collection 多个对象
   */
  public static <T> void deleteObject(final Collection<T> collection) {
    RBatch batch = redissonClient.createBatch();
    collection.forEach(t -> batch.getBucket(t.toString()).deleteAsync());
    batch.execute();
  }

  /**
   * 检查缓存对象是否存在
   *
   * @param key 缓存的键值
   */
  public static boolean isExistsObject(final String key) {
    return redissonClient.getBucket(key).isExists();
  }

  /**
   * 缓存List数据
   *
   * @param key      缓存的键值
   * @param dataList 待缓存的List数据
   * @return 缓存的对象
   */
  public static <T> boolean setCacheList(final String key, final List<T> dataList) {
    RList<T> rList = redissonClient.getList(key);
    rList.delete();
    return rList.addAll(dataList);
  }

  /**
   * 注册List监听器
   * <p>
   * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
   *
   * @param key      缓存的键值
   * @param listener 监听器配置
   */
  public static <T> void addListListener(final String key, final ObjectListener listener) {
    RList<T> rList = redissonClient.getList(key);
    rList.addListener(listener);
  }

  /**
   * 获得缓存的list对象
   *
   * @param key 缓存的键值
   * @return 缓存键值对应的数据
   */
  public static <T> List<T> getCacheList(final String key) {
    RList<T> rList = redissonClient.getList(key);
    return rList.readAll();
  }

  /**
   * 缓存Set
   *
   * @param key     缓存键值
   * @param dataSet 缓存的数据
   * @return 缓存数据的对象
   */
  public static <T> boolean setCacheSet(final String key, final Set<T> dataSet) {
    RSet<T> rSet = redissonClient.getSet(key);
    return rSet.addAll(dataSet);
  }

  /**
   * 缓存Set添加值
   *
   * @param key   缓存键值
   * @param value 缓存的数据
   * @return 缓存数据的对象
   */
  public static <T> boolean setCacheSet(final String key, final T value) {
    RSet<T> rSet = redissonClient.getSet(key);
    return rSet.add(value);
  }

  /**
   * 从set结构中查询.
   *
   * @param key   set 的key
   * @param value 要查询的值
   * @param count set scan 参考数量.
   * @param limit 返回限制数量.
   * @return 查询结果
   */
  public static <V> List<V> retrieveFromSetCache(
      final String key,
      final String value,
      int count,
      int limit) {
    final RSet<V> rSet = redissonClient.getSet(key);
    return rSet.stream(value, count).limit(limit).toList();
  }

  /**
   * 删除Set值
   *
   * @param key   缓存键值
   * @param value 缓存的数据
   * @return 缓存数据的对象
   */
  public static <T> boolean delCacheSet(final String key, final T value) {
    RSet<T> rSet = redissonClient.getSet(key);
    return rSet.remove(value);
  }

  /**
   * 注册Set监听器
   * <p>
   * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
   *
   * @param key      缓存的键值
   * @param listener 监听器配置
   */
  public static <T> void addSetListener(final String key, final ObjectListener listener) {
    RSet<T> rSet = redissonClient.getSet(key);
    rSet.addListener(listener);
  }

  /**
   * 获得缓存的set
   *
   * @param key 缓存的key
   * @return set对象
   */
  public static <T> Set<T> getCacheSet(final String key) {
    RSet<T> rSet = redissonClient.getSet(key);
    return rSet.readAll();
  }

  /**
   * 判断set中是否包含某个值
   *
   * @param key key.
   * @param obj obj.
   * @return 是否存在.
   */
  public static <T> boolean cacheSetContains(final String key, final T obj) {
    RSet<T> rSet = redissonClient.getSet(key);
    return rSet.contains(obj);
  }

  /**
   * 缓存Map
   *
   * @param key     缓存的键值
   * @param dataMap 缓存的数据
   */
  public static <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
    if (dataMap != null) {
      RMap<String, T> rMap = redissonClient.getMap(key);
      rMap.putAll(dataMap);
    }
  }

  /**
   * 注册Map监听器
   * <p>
   * key 监听器需开启 `notify-keyspace-events` 等 redis 相关配置
   *
   * @param key      缓存的键值
   * @param listener 监听器配置
   */
  public static <T> void addMapListener(final String key, final ObjectListener listener) {
    RMap<String, T> rMap = redissonClient.getMap(key);
    rMap.addListener(listener);
  }

  /**
   * 获得缓存的Map
   *
   * @param key 缓存的键值
   * @return map对象
   */
  public static <T> Map<String, T> getCacheMap(final String key) {
    RMap<String, T> rMap = redissonClient.getMap(key);
    return rMap.getAll(rMap.keySet());
  }

  /**
   * 获得缓存Map的key列表
   *
   * @param key 缓存的键值
   * @return key列表
   */
  public static <T> Set<String> getCacheMapKeySet(final String key) {
    RMap<String, T> rMap = redissonClient.getMap(key);
    return rMap.keySet();
  }

  /**
   * 往Hash中存入数据
   *
   * @param key   Redis键
   * @param hKey  Hash键
   * @param value 值
   */
  public static <T> void setCacheMapValue(final String key, final String hKey, final T value) {
    RMap<String, T> rMap = redissonClient.getMap(key);
    rMap.put(hKey, value);
  }

  /**
   * 获取Hash中的数据
   *
   * @param key  Redis键
   * @param hKey Hash键
   * @return Hash中的对象
   */
  public static <T> T getCacheMapValue(final String key, final String hKey) {
    RMap<String, T> rMap = redissonClient.getMap(key);
    return rMap.get(hKey);
  }

  /**
   * 删除Hash中的数据
   *
   * @param key  Redis键
   * @param hKey Hash键
   * @return Hash中的对象
   */
  public static <T> T delCacheMapValue(final String key, final String hKey) {
    RMap<String, T> rMap = redissonClient.getMap(key);
    return rMap.remove(hKey);
  }

  /**
   * 删除Hash中的数据
   *
   * @param key   Redis键
   * @param hKeys Hash键
   */
  public static <T> void delMultiCacheMapValue(final String key, final Set<String> hKeys) {
    RBatch batch = redissonClient.createBatch();
    RMapAsync<String, T> rMap = batch.getMap(key);
    for (String hKey : hKeys) {
      rMap.removeAsync(hKey);
    }
    batch.execute();
  }

  /**
   * 获取多个Hash中的数据
   *
   * @param key   Redis键
   * @param hKeys Hash键集合
   * @return Hash对象集合
   */
  public static <K, V> Map<K, V> getMultiCacheMapValue(final String key, final Set<K> hKeys) {
    RMap<K, V> rMap = redissonClient.getMap(key);
    return rMap.getAll(hKeys);
  }

  /**
   * 设置原子值
   *
   * @param key   Redis键
   * @param value 值
   */
  public static void setAtomicValue(String key, long value) {
    RAtomicLong atomic = redissonClient.getAtomicLong(key);
    atomic.set(value);
  }

  /**
   * 获取原子值
   *
   * @param key Redis键
   * @return 当前值
   */
  public static long getAtomicValue(String key) {
    RAtomicLong atomic = redissonClient.getAtomicLong(key);
    return atomic.get();
  }

  /**
   * 递增原子值
   *
   * @param key Redis键
   * @return 当前值
   */
  public static long incrAtomicValue(String key) {
    RAtomicLong atomic = redissonClient.getAtomicLong(key);
    return atomic.incrementAndGet();
  }

  /**
   * 递减原子值
   *
   * @param key Redis键
   * @return 当前值
   */
  public static long decrAtomicValue(String key) {
    RAtomicLong atomic = redissonClient.getAtomicLong(key);
    return atomic.decrementAndGet();
  }

  /**
   * 添加位置坐标
   *
   * @param key       key
   * @param longitude 经度
   * @param latitude  纬度
   * @param member    值
   */
  public static <V> long geoAdd(String key, double longitude, double latitude, V member) {
    RGeo<V> geo = redissonClient.getGeo(key);
    return geo.add(longitude, latitude, member);
  }

  /**
   * 添加位置坐标
   *
   * @param key     key
   * @param entries 要添加的位置.
   * @return 添加的数量
   */
  public static long geoAdd(String key, GeoEntry... entries) {
    RGeo<Object> geo = redissonClient.getGeo(key);
    return geo.add(entries);
  }

  /**
   * 获得距离
   *
   * @param key          key.
   * @param firstMember  第一个位置.
   * @param secondMember 第二个位置.
   * @param geoUnit      距离单位
   * @return 距离.
   */
  public static <V> Double geoDist(String key, V firstMember, V secondMember, GeoUnit geoUnit) {
    RGeo<Object> geo = redissonClient.getGeo(key);
    return geo.dist(firstMember, secondMember, geoUnit);
  }

  /**
   * 根据位置获取坐标.
   *
   * @param members 要获取的位置
   * @return 位置坐标。
   */
  @SafeVarargs
  public static <V> Map<V, GeoPosition> geoPos(String key, V... members) {
    RGeo<V> geo = redissonClient.getGeo(key);
    return geo.pos(members);
  }

  /**
   * 查询位置
   *
   * @param key  key.
   * @param args 查询参数.
   * @return 符合条件的位置.
   */
  public static <V> List<V> geoSearch(String key, GeoSearchArgs args) {
    final RGeo<V> geo = redissonClient.getGeo(key);

    return geo.search(args);
  }

  /**
   * 设置json对象
   *
   * @param key    key
   * @param value  value
   * @param tClass 对象类
   */
  public static <T> void jsonSet(String key, T value, Class<T> tClass) {
    final RJsonBucket<T> jsonBucket = redissonClient.getJsonBucket(key, new JacksonCodec<>(tClass));
    jsonBucket.set(value);
  }

  /**
   * 设置json对象
   *
   * @param key    key
   * @param value  value
   * @param tClass 对象类
   */
  public static <V, T> void jsonSet(String key, String path, V value, Class<T> tClass) {
    final RJsonBucket<T> jsonBucket = redissonClient.getJsonBucket(key, new JacksonCodec<>(tClass));
    jsonBucket.set(path, value);
  }

  /**
   * 获取json对象
   *
   * @param key    key
   * @param tClass 对象类
   * @return json对象
   */
  public static <T> T jsonGet(String key, Class<T> tClass) {
    final RJsonBucket<T> jsonBucket = redissonClient.getJsonBucket(key, new JacksonCodec<>(tClass));
    return jsonBucket.get();
  }

  /**
   * 获取json对象
   *
   * @param key    key
   * @param tClass 对象类
   * @return json对象
   */
  public static <V, T> V jsonGet(String key, TypeReference<V> valueTypeReference, Class<T> tClass,
      String... paths) {
    final RJsonBucket<T> jsonBucket = redissonClient.getJsonBucket(key, new JacksonCodec<>(tClass));

    return jsonBucket.get(new JacksonCodec<>(valueTypeReference), paths);
  }


  /**
   * 获得缓存的基本对象列表
   *
   * @param pattern 字符串前缀
   * @return 对象列表
   */
  public static Collection<String> keys(final String pattern) {
    Stream<String> stream = redissonClient.getKeys().getKeysStreamByPattern(pattern);
    return stream.toList();
  }

  /**
   * 删除缓存的基本对象列表
   *
   * @param pattern 字符串前缀
   */
  public static void deleteKeys(final String pattern) {
    redissonClient.getKeys().deleteByPattern(pattern);
  }

  /**
   * 检查redis中是否存在key
   *
   * @param key 键
   */
  public static Boolean hasKey(String key) {
    RKeys rKeys = redissonClient.getKeys();
    return rKeys.countExists(key) > 0;
  }

}
